/**/

#include "administrator.hpp"
#include "edge.hpp"
#include "node.hpp"
#include <cmath>

Administrator::Administrator()
{
}


Administrator::Administrator(std::string &fileToParseN, std::string &fileToParseE,std::string &fileToParseO,
                                                         Logger *logger)
{
        this->create(fileToParseN,fileToParseE,fileToParseO,logger);
}

void Administrator::create(std::string &fileToParseN, std::string &fileToParseE,std::string &fileToParseO, Logger *logger)
{
        this->logger = logger;

        GraphFactory graphFactory(fileToParseN,fileToParseE,fileToParseO,this->logger,&this->graph);

        graphFactory.generateGraph();
}



int Administrator::search(std::string originRoadName, int originRoadHeight,ObjectType objectType,int priority)
{

	this->priority=priority;

	List<Object> tempList;

	this->destinyObjectType = objectType;

	// Para cada objeto del grafo del tipo buscado
	ListIterator<Object> it = this->graph.getObjects()->iterator();
	while(it.hasNext())
	{
		// Datos necesarios
		Object *obj = it.next();
		if(obj->getType() == objectType)
		{
			float tempHeight = searchPath(originRoadName,originRoadHeight, graph.getEdge(obj->getEdgeId())->getRoadName(),obj->getHeight(),priority,false);
			if(tempHeight >= 0)
			{
				obj->setTemporalWeight(tempHeight);
				tempList.add(obj);
			}
		}
	}

	Object *obj = tempList.popFirst();

	//Despues de ordenarlos saco el primero
	if(obj)
	{
		int toReturn=search(originRoadName,originRoadHeight, graph.getEdge(obj->getEdgeId())->getRoadName(),obj->getHeight(),priority);
		//Lo pongo al final pq el search por calle y altura sino lo pisa
		this->searchingBy=1;
		return 1;
	}
	else
	{
		return 0;
	}



}


int Administrator::search(std::string originRoadName, int originRoadHeight, std::string destinyRoadName, int destinyRoadHeight , int priority)
{

	this->priority=priority;

	this->destinyRoadHeight=destinyRoadHeight;
	this->destinyRoadName=destinyRoadName;
	this->searchingBy=0;

	char buffer [33];
	char buffer2 [33];

	Edge *edgeO = this->graph.getEdge(originRoadName,originRoadHeight);
	Edge *edgeD = this->graph.getEdge(destinyRoadName,destinyRoadHeight);
    if((edgeO)&&(edgeD))
    {
		float busqueda;
		busqueda = searchPath(originRoadName,originRoadHeight,destinyRoadName,destinyRoadHeight,priority,true);
		if (busqueda>0)
		{
			if(edgeO==edgeD && ((edgeO->getInitialHeight() < edgeO->getFinalHeight() && originRoadHeight < destinyRoadHeight ) || (edgeO->getInitialHeight() > edgeO->getFinalHeight() && originRoadHeight > destinyRoadHeight) ))
			{
				//El origen y el destino se encuentra en la misma calle y en el mismo sentido
				//En este caso no se ejecuta el siguiente nivel de busqueda (de nodo a nodo) por lo cual limpio todo aca.
				this->graph.cleanBestPathValues();

				_itoa_s(originRoadHeight,buffer,10);
				_itoa_s(destinyRoadHeight,buffer2,10);
				roadPathFound = edgeO->getRoadName() + " "+ buffer + "-" + buffer2;
				edgeD->setBelongToMP(originRoadHeight,destinyRoadHeight);


			}
			else
			{
				//Estos valores van a ser usados por el constructor graficador
				//Seteo que parte del edge pertenece al camino minimo.

				_itoa_s(originRoadHeight,buffer,10);
				_itoa_s(edgeO->getFinalHeight(),buffer2,10);


				roadPathFound = edgeO->getRoadName() + " "+ buffer + "-" + buffer2 + "-> "+roadPathFound;


				_itoa_s(edgeD->getInitialHeight(),buffer,10);
				_itoa_s(destinyRoadHeight,buffer2,10);

				roadPathFound = roadPathFound + edgeD->getRoadName()+ " "+ buffer + "-" + buffer2;


				edgeO->setBelongToMP(originRoadHeight,edgeO->getFinalHeight());
				edgeD->setBelongToMP(edgeD->getInitialHeight(),destinyRoadHeight);
			}
			return 1;
		}
		return 0;
	}
    else
    {
            std::string detail = "Edge ";
			if (!edgeO)
            {
                    detail += "origen inexistente";
                    this->errorCode=11;
            }
            else
            {
                    detail += "destino inexistente";
                    this->errorCode=21;
            }
            this->logger->reportErrorMessage("SearchAlgorithm","execute",detail);

            return -1;

    }




}


int Administrator::search(std::string originId, std::string destinyId, int priority)
{

	this->priority=priority;
	

    Node *nodeO = this->graph.getNode(originId);
    Node *nodeD = this->graph.getNode(destinyId);
    if((nodeO)&&(nodeD))
    {

            std::string path;
			std::string roadPath;

			float ret = searchPath(originId,destinyId,priority);

			if(ret >=0)
            {
                Node *destiny = this->graph.getNode(destinyId);

                Edge *previous = destiny->getMinimunPathEdge();



                path="Nodo "+destinyId;

				destiny->setBelongToMP();

                while(previous != NULL)
                {       

					char buffer [33];
					char buffer2 [33];
					std::string prevOriginId = previous->getOriginId();
                    std::string prevId = previous->getId();

                    path="Nodo "+prevOriginId+" -> Arista "+ prevId +" -> "+path;
					
					_itoa_s(previous->getInitialHeight(),buffer,10);
					_itoa_s(previous->getFinalHeight(),buffer2,10);
				

					roadPath= previous->getRoadName() + " "+ buffer + "-" + buffer2 + "-> "+roadPath;
					
					//Arista previa si de camino minimo en su completitud
					previous->setBelongToMP();

                    destiny = this->graph.getNode(prevOriginId);

					//Nodo destino pertence al MP
					destiny->setBelongToMP();

                    previous = destiny->getMinimunPathEdge();
                }
                this->pathFound=path;
				this->roadPathFound = roadPath;
				return 1;
            }
			return 0;
	}
    else
    {
            std::string detail = "Nodo ";
            if (!nodeO)
            {
                    detail += "origen inexistente";
                    this->errorCode=10;
            }
            else
            {
                    detail += "destino inexistente";
                    this->errorCode=20;
            }
            this->logger->reportErrorMessage("SearchAlgorithm","execute",detail);

            return -1;

    }

}



float Administrator::searchPath(std::string originRoadName, int originRoadHeight, std::string destinyRoadName, int destinyRoadHeight , int priority,bool setPath)
{



	Edge *edgeO = this->graph.getEdge(originRoadName,originRoadHeight);
	Edge *edgeD = this->graph.getEdge(destinyRoadName,destinyRoadHeight);
    if((edgeO)&&(edgeD))
    {
		//float busqueda;

		if(edgeO==edgeD && ((edgeO->getInitialHeight() < edgeO->getFinalHeight() && originRoadHeight < destinyRoadHeight ) || (edgeO->getInitialHeight() > edgeO->getFinalHeight() && originRoadHeight > destinyRoadHeight) ))
		{
			//El origen y el destino se encuentra en la misma calle y en el mismo sentido
			if(priority==0) //distancia
			{
    			return ((float)abs(originRoadHeight-destinyRoadHeight))/((float)abs(edgeO->getInitialHeight() - edgeO->getFinalHeight())) * edgeO->getWeight().getLength();
			}
			else
			{ // tiempo
				
				return ((float)abs(originRoadHeight-destinyRoadHeight))/((float)abs(edgeO->getInitialHeight() - edgeO->getFinalHeight())) * edgeO->getWeight().getLength() / edgeO->getWeight().getVelocity();
			}

		}
		else
		{
			float result;
			if(setPath)
			{
				//Este metodo escribe el path por ser el publico
				result = search(edgeO->getDestinyId(),edgeD->getOriginId(),priority);
			}
			else
			{
				result = searchPath(edgeO->getDestinyId(),edgeD->getOriginId(),priority);
			}
			if(result>=0)
			{
				if(priority==0) //distancia
				{
					float distanciaEntreNodos=graph.getNode(edgeD->getOriginId())->getTemporalWeight();
					float distanciaPedacitoOrigen = ((float)abs(originRoadHeight  - edgeO->getFinalHeight()  )/abs(edgeO->getInitialHeight() - edgeO->getFinalHeight())) * edgeO->getWeight().getLength();
					float distanciaPedacitoFin =	((float)abs(destinyRoadHeight - edgeD->getInitialHeight())/abs(edgeD->getInitialHeight() - edgeD->getFinalHeight())) * edgeD->getWeight().getLength();
					return  distanciaEntreNodos+ distanciaPedacitoOrigen  + distanciaPedacitoFin;
				}
				else
				{ // tiempo
					float tiempoEntreNodos=graph.getNode(edgeD->getOriginId())->getTemporalWeight();
					float tiempoPedacitoOrigen = ((float) 
														abs(originRoadHeight  - edgeO->getFinalHeight()) 
													  / abs(edgeO->getInitialHeight() - edgeO->getFinalHeight())
												  ) * edgeO->getWeight().getLength() / edgeO->getWeight().getVelocity();

					float tiempoPedacitoFin =    ((float)
														abs(destinyRoadHeight - edgeD->getInitialHeight())
													  / abs(edgeD->getInitialHeight() - edgeD->getFinalHeight())
												  ) * edgeD->getWeight().getLength() / edgeO->getWeight().getVelocity();

					return   tiempoEntreNodos + tiempoPedacitoOrigen +tiempoPedacitoFin ;
						 
				}
			}
			else
			{
				return -1;
			}
		}
	}
    else
    {
            return -1;
    }

	
}


float Administrator::searchPath(std::string originId, std::string destinyId, int priority)
{

	//Este es el algoritmo mas profundo y antes de correrse limpia los valores de BP en el grafo.
	this->graph.cleanBestPathValues();

        Node *nodeO = this->graph.getNode(originId);
        Node *nodeD = this->graph.getNode(destinyId);
		
        if((nodeO)&&(nodeD))
        {

                SearchAlgorithm searchAlgorithm(this->logger);
                return searchAlgorithm.execute(&this->graph,nodeO,nodeD,priority);
        }
        else
        {
                return -1;
        }

}

bool Administrator::isGraphCreated(){
    if (this->graph.getNumberOfNodes() > 0)
        return true;
    return false;
}

void Administrator::cleanBestPathValues()
{
        this->graph.cleanBestPathValues();
}




string Administrator::getPath()
{
        return this->pathFound;
}

string Administrator::getRoadPath()
{
        return this->roadPathFound;
}


int Administrator::getErrorCode()
{
        return this->errorCode;
}

Graph *Administrator::getGraph(){
    return &(this->graph);
}


Administrator::~Administrator(){}

int Administrator::updatePosition(std::string roadName,int roadHeight)
{
	if(this->searchingBy == 0)
	{
		return this->search(roadName,roadHeight,this->destinyRoadName,this->destinyRoadHeight,this->priority);
	}
	else
	{
		return this->search(roadName,roadHeight,this->destinyObjectType,this->priority);
	}
}